package org.datascooter;

import java.io.File;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

import org.datascooter.bundle.BundleUtils;
import org.datascooter.bundle.DSMapper;
import org.datascooter.cache.ConnectionManager;
import org.datascooter.exception.DataScooterException;
import org.datascooter.exception.EntityNotMappedException;
import org.datascooter.impl.DataManager;
import org.datascooter.impl.DataSource;
import org.datascooter.impl.DummyBuilderProvider;
import org.datascooter.impl.DummyConnector;
import org.datascooter.impl.DummyConnectorProvider;
import org.datascooter.impl.DummyDataSourceProvider;
import org.datascooter.impl.SnipManager;
import org.datascooter.inface.IBundleProvider;
import org.datascooter.inface.IConnectorProvider;
import org.datascooter.inface.IContextConnector;
import org.datascooter.inface.IDataManager;
import org.datascooter.inface.IDataSource;
import org.datascooter.inface.IDataSourceProvider;
import org.datascooter.inface.ISnipBuilderProvider;
import org.datascooter.inface.ISnipManager;
import org.datascooter.parser.DOMReader;
import org.datascooter.parser.DSBuilderProvider;
import org.datascooter.parser.DSConnectorProvider;
import org.datascooter.parser.DSMappingProvider;
import org.datascooter.utils.DSSettings;
import org.datascooter.utils.DataSourceHandler;
import org.datascooter.utils.SnipUtils;
import org.datascooter.utils.policy.SequencePolicy;

/**
 * The entry point for obtaining data from different data stores after start of application this class reads extension points and records the all available database contexts, data managers, update
 * servers and picks the default ones
 * 
 * @author nemo
 * 
 */
public final class DataScooter {

	private static final String SCHEMA = "schema";
	private static final String BUILDER = "builder";
	private static final String CONNECTOR = "connector";
	private static final String PLUGIN_XML = "plugin.xml";
	private static final String DATASCOOTER_PROPERTIES = "/datascooter.properties";
	private static final String MAPPING_XML = "/mapping.xml";
	private static final String JDBC = "jdbc:";
	private static final String DEFAULT = "default";
	private static final String DATASOURCE_PROPERTIES = "/datasource.properties";
	public static final String ID = "00000000-0000-0000-0000-000000000000";//$NON-NLS-1$
	public static final String JDBC_H2_FILE = "jdbc:h2:file:";
	private static IDataManager defaultManager;
	private static IDataSourceProvider dataSourceProvider;
	private static IConnectorProvider connectorProvider;
	private static ISnipBuilderProvider builderProvider;
	private static Map<String, IDataManager> manMap = new ConcurrentHashMap<String, IDataManager>();

	/**
	 * The minimalist start configuration with a single data source and explicit data base configuration and without verify tables or provide additional tables in database like squences table
	 * 
	 * @param propertiesPath
	 * @param url
	 * @param user
	 * @param password
	 */
	public static void startSilent(String propertiesPath, String url, String user, String password) {
		DSSettings.setVerifyTables(false);
		DSSettings.setSequencePolicy(SequencePolicy.NO_SEQUENCES);
		startZero(propertiesPath, url, user, password);
	}

	/**
	 * The minimalist start configuration with a single data source and explicit data base configuration 'propertiesPath' may be null so will be used default properties, if 'url' do not start with
	 * 'jdbc:' but just simple directory path - so will be used H2 data base. If 'url' starts with 'jdbc:' - so will be loaded JDBC-driver for required database (be sure if required driver exists and
	 * in classpath)
	 * 
	 * @param propertiesPath
	 * @param url
	 * @param user
	 * @param password
	 */
	public static void startZero(String propertiesPath, String url, String user, String password) {
		try {
			DataSource dataSource = new DataSource(DEFAULT, (!url.startsWith(JDBC) ? JDBC_H2_FILE : SnipUtils.NN) + url, user, password, true);
			start(preparePath(propertiesPath), new DummyBuilderProvider(dataSource.getBuilder()), new DummyConnectorProvider(dataSource.getConnector()), new DummyDataSourceProvider(dataSource));
		} catch (Exception e) {
			throw new DataScooterException(e);
		}
	}

	public static void startZero(String propertiesPath, String mappingPath, javax.sql.DataSource source) {
		try {
			String url = source.getConnection().getMetaData().getURL();
			url = (!url.startsWith(JDBC) ? JDBC_H2_FILE : SnipUtils.NN) + url;
			String user = source.getConnection().getMetaData().getUserName();
			int indexOf = user.indexOf("@");
			if (indexOf > 0) {
				user = user.substring(0, indexOf);
			}
			DataSource dataSource = new DataSource(DEFAULT, url, user, "", true);
			DSSettings.setVerifyTables(false);
			start(preparePath(propertiesPath), new DummyBuilderProvider(dataSource.getBuilder()), new DummyConnectorProvider(new DummyConnector(source, dataSource)), new DummyDataSourceProvider(
				dataSource), new DSMappingProvider(new DOMReader(SCHEMA, preparePath(mappingPath))));
		} catch (Exception e) {
			throw new DataScooterException(e);
		}
	}

	/**
	 * The minimalist start configuration with a single data source and reading from an applicationPath a data base configuration
	 * 
	 * @param applicationPath
	 */
	public static void startZero(String applicationPath) {
		try {
			String applicationPath2 = preparePath(applicationPath);
			DataSourceHandler handler = new DataSourceHandler(applicationPath2 + DATASOURCE_PROPERTIES);
			DataSource dataSource = handler.getCurrent();
			File file = new File(applicationPath2, MAPPING_XML);
			if (file.exists()) {
				start(applicationPath2 + DATASCOOTER_PROPERTIES, new DummyBuilderProvider(dataSource.getBuilder()), new DummyConnectorProvider(dataSource.getConnector()), new DummyDataSourceProvider(
					dataSource), new DSMappingProvider(new DOMReader(SCHEMA, file.getPath())));
			} else {
				start(applicationPath2 + DATASCOOTER_PROPERTIES, new DummyBuilderProvider(dataSource.getBuilder()), new DummyConnectorProvider(dataSource.getConnector()), new DummyDataSourceProvider(
					dataSource));
			}
		} catch (Exception e) {
			throw new DataScooterException(e);
		}
	}

	/**
	 * Allmost full configuration with reading configs from plugin.xml files but without eclipse environment
	 * 
	 * @param pluginDir
	 * @param url
	 * @param user
	 * @param password
	 */
	public static void startDom(String pluginDir, String url, String user, String password, IBundleProvider... bundleProviders) {
		url = (!url.startsWith(JDBC) ? JDBC_H2_FILE : SnipUtils.NN) + url;
		String contextId = SnipUtils.getContext(url);
		String contextStr = pluginDir + ".db." + contextId + File.separator + PLUGIN_XML;
		start(null, new DSBuilderProvider(new DOMReader(BUILDER, contextStr)), new DSConnectorProvider(new DOMReader(CONNECTOR, contextStr)), new DummyDataSourceProvider(new DataSource("simple", url,
			user, password, true)), bundleProviders);
	}

	/**
	 * Start with remote SnipManager
	 * 
	 * @param manager
	 * @param propertiesPath
	 * @param bundleProviders
	 */
	public static void startRemote(ISnipManager manager, String propertiesPath, ISnipBuilderProvider builderProviderIn, IBundleProvider... bundleProviders) {
		try {
			clear();
			DSSettings.init(preparePath(propertiesPath));
			DSMapper.setBundleProviders(bundleProviders);
			DSMapper.explore();
			builderProvider = builderProviderIn;
			builderProvider.explore();
			ConnectionManager.register(manager.getConnector(), DSSettings.getConnectionPoolSize(), DSSettings.getWaitConnectionTimeout());
			defaultManager = new DataManager(manager, builderProvider.getItem(manager.getContextId()).copy());
		} catch (Exception e) {
			throw new DataScooterException(e);
		}
	}

	/**
	 * Base start mehod
	 * 
	 * @param propertiesPath
	 * @param builderPrr
	 * @param connectorPrr
	 * @param dataSourcePrr
	 * @param bundleProviders
	 */
	public static void start(String propertiesPath, ISnipBuilderProvider builderPrr, IConnectorProvider connectorPrr, IDataSourceProvider dataSourcePrr, IBundleProvider... bundleProviders) {
		try {
			clear();
			DSSettings.init(propertiesPath);
			builderProvider = builderPrr;
			connectorProvider = connectorPrr;
			dataSourceProvider = dataSourcePrr;
			connectorProvider.explore();
			builderProvider.explore();
			dataSourceProvider.explore();
			BundleUtils.prepareMapper();
			DSMapper.setBundleProviders(bundleProviders);
			DSMapper.explore();
			defaultManager = buildManager(dataSourceProvider.getDefault());
		} catch (Exception e) {
			throw new DataScooterException(e);
		}
	}

	protected static String preparePath(String propertiesPath) {
		if (propertiesPath != null && (propertiesPath.startsWith("/") || propertiesPath.startsWith("."))) {
			URL resource = DataScooter.class.getResource(propertiesPath);
			if (resource != null) {
				return resource.getPath();
				// File ff = new File(propertiesPath);
				// try {
				// // System.out.println("----dddd-----------------df----------------" + ff.getCanonicalPath());
				// } catch (IOException e) {
				// e.printStackTrace();
				// }
			} else {
				// throw new DataScooterException("Wrong path :  " + propertiesPath);
			}
		}
		return propertiesPath;
	}

	public static IDataManager getDefault() {
		return defaultManager;
	}

	public static IDataManager getManager(String dname) {
		IDataManager manager = manMap.get(dname);
		if (manager == null) {
			IDataSource dataSource = dataSourceProvider.getItem(dname);
			if (dataSource == null) {
				Logger.getLogger("****Datascooter").info("***Datasource not found for dname: " + dname);
				return null;
			}
			manager = buildManager(dataSource);
			manMap.put(dname, manager);
		}
		return manager;
	}

	private static IDataManager buildManager(IDataSource dataSource) {
		if (dataSource == null) {
			return null;
		}
		try {
			IContextConnector contextConnector = dataSource.getConnector();
			ConnectionManager.register(contextConnector, DSSettings.getConnectionPoolSize(), DSSettings.getWaitConnectionTimeout());
			return new DataManager(new SnipManager(contextConnector), dataSource.getBuilder());
		} catch (Exception e) {
			throw new DataScooterException(e);
		}
	}

	public static void putSource(DataSource dataSource) {
		dataSourceProvider.putSource(dataSource);
	}

	public static void removeDataSource(String dname) {
		dataSourceProvider.removeSource(dname);
	}

	public static List<String> getAvailableContexts() {
		return connectorProvider.getAvailableContexts();
	}

	public static void clear() throws EntityNotMappedException, SecurityException, NoSuchMethodException {
		if (defaultManager != null) {
			defaultManager.close();
		}
		defaultManager = null;
		dataSourceProvider = null;
		connectorProvider = null;
		builderProvider = null;
		Iterator<IDataManager> iterator = manMap.values().iterator();
		while (iterator.hasNext()) {
			iterator.next().close();
		}
		manMap.clear();
		DSMapper.clear();
	}
}
